//------------------------------------------------------------
//  Filename: eeprom_iic.v
//   
//  Author  : wlduan@gmail.com
//  Revise  : 2016-10-07 17:01
//  Description: 
//   
//  Copyright (C) 2014, YRBD, Inc. 					      
//  All Rights Reserved.                                       
//-------------------------------------------------------------
//
`timescale 1ns/1ps
 
module EEPROM_IIC #(
    parameter MAX_TRANS  = 9,
    parameter CLK_DIVBIT = 10    
)
( 
    input  wire        clk,
    input  wire        rst,  
    
    input  wire        eeprom_addr_size, 
    input  wire [7:0]  eeprom_devid,
    input  wire [31:0] eeprom_ctrl,
    output reg  [15:0] eeprom_data_read,
    output reg         eeprom_trans_over,

    output wire        eeprom_sclk,
    inout  wire        eeprom_sda
);      

//--------------------------------------------------------
wire[6:0]   dev_id      = eeprom_devid[7:1];      
wire        s_op        = eeprom_ctrl[24];
wire[15:0]  sub_address = eeprom_ctrl[23:8];
wire[7:0]   write_data  = eeprom_ctrl[7:0];
wire        op_start    = eeprom_ctrl[31];
wire[1:0]   s_start     = 2'b10;
wire[1:0]   s_stop      = 2'b01;
wire        s_nack      = 1'b0;
//--------------------------------------------------------
wire posedge_div_clk;
//--------------------------------------------------------
reg op_start_ff1;
always @(posedge clk) op_start_ff1 <= op_start;
//--------------------------------------------------------
wire posedge_op_start = op_start&(~op_start_ff1);
//--------------------------------------------------------
//  IIC logic
//--------------------------------------------------------
//--------------------------------------------------------
reg       ack_detec;
reg[1:0 ] stage_cntr ; 
reg[3:0 ] phase_cntr ; 
reg[3:0 ] step_cntr  ;  
//--------------------------------------------------------
reg[CLK_DIVBIT:0] counter_driver;
//--------------------------------------------------------
localparam IDLE   = 8'b0000_0001;
localparam START  = 8'b0000_0100;
localparam PH_ID  = 8'b0000_1000;
localparam PH_A0  = 8'b0001_0000;
localparam PH_A1  = 8'b0010_0000;
localparam PH_D0  = 8'b0100_0000;
localparam STOP   = 8'b1000_0000;
//--------------------------------------------------------
reg[7:0] cur_stat;
reg[7:0] nxt_stat;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        cur_stat <= IDLE;
    end 
    else begin 
        cur_stat <= nxt_stat;  
    end 
end 
//--------------------------------------------------------
wire[7:0] PH_AX = (eeprom_addr_size==1)? PH_A0:PH_A1; 
//--------------------------------------------------------
always @(*) begin
    case (cur_stat)
        IDLE   : begin 
            nxt_stat = (posedge_op_start)? START:IDLE;
        end
        START  : begin 
            nxt_stat = (step_cntr >= 3)? PH_ID:START;
        end
        PH_ID  : begin 
            if(step_cntr >= MAX_TRANS) begin
                if(ack_detec)begin
                    nxt_stat = ((stage_cntr > 1)? PH_D0:PH_AX);
                end
                else begin
                    nxt_stat = STOP;
                end
            end
            else begin
                nxt_stat = PH_ID;
            end
        end
        PH_A0  : begin 
            if (step_cntr >= MAX_TRANS) begin
                if(ack_detec)begin
                    nxt_stat = PH_A1;
                end
                else begin
                    nxt_stat = STOP;
                end
            end
            else begin
                nxt_stat = PH_A0;
            end
        end
        PH_A1  : begin 
            if (step_cntr >= MAX_TRANS) begin
                if(ack_detec)begin
                    nxt_stat = ((stage_cntr == 1)&&(s_op == 1))? STOP:PH_D0;
                end
                else begin
                    nxt_stat = STOP;
                end
            end
            else begin
                nxt_stat = PH_A1;
            end       
        end
        PH_D0  : begin 
            if (step_cntr >= MAX_TRANS) begin
                nxt_stat = STOP;
            end
            else begin
                nxt_stat = PH_D0;
            end
        end
        STOP   : begin 
            nxt_stat = (step_cntr >= 3)? (((stage_cntr == 1)&&(s_op == 1))? START:IDLE):STOP;
        end  
        default: begin
            nxt_stat = IDLE;
        end      
    endcase 
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        counter_driver <= 'b0;        
    end 
    else if(cur_stat == IDLE) begin 
        counter_driver <= 'b0;        
    end 
    else if(cur_stat == nxt_stat )begin
        counter_driver <= counter_driver + 'b1;        
    end
    else begin
        counter_driver <= 'b0;        
    end
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        phase_cntr <= 2'b0;        
    end 
    else if(cur_stat == IDLE)begin
        phase_cntr <= 2'b0;        
    end
    else if(cur_stat == nxt_stat)begin
       if(posedge_div_clk) phase_cntr <= phase_cntr + 2'b1;    
    end
    else begin 
        phase_cntr <= 2'b0;        
    end 
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        step_cntr <= 2'b0;        
    end 
    else if(cur_stat == IDLE)begin
        step_cntr <= 2'b0;        
    end
    else if(cur_stat == nxt_stat )begin 
        if((posedge_div_clk)&&(&phase_cntr)) step_cntr <= step_cntr + 2'b1;    
    end 
    else begin 
        step_cntr <= 'b0;    
    end     
end
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        stage_cntr <= 2'b0;        
    end 
    else if(nxt_stat == IDLE)begin
        stage_cntr <= 2'b0;        
    end
    else if(nxt_stat == cur_stat) begin
        stage_cntr <= stage_cntr;   
    end
    else if(nxt_stat == PH_ID)begin 
        stage_cntr <= stage_cntr + 2'b1;    
    end 
end 
//--------------------------------------------------------
assign posedge_div_clk = (counter_driver == 0)?1'b1:1'b0;
//--------------------------------------------------------
reg[7:0] data_load;
reg      eeprom_sda_o;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        data_load <= 8'hf;    
    end 
    else if(nxt_stat == cur_stat) begin
        if(posedge_div_clk&&(phase_cntr == 4'ha)) data_load <= {data_load[6:0],1'b0};
    end
    else if(nxt_stat == START) begin 
        data_load <= 8'h80;    
    end
    else if(nxt_stat == STOP) begin
        data_load <= 8'h20;    
    end
    else if(nxt_stat == PH_ID) begin 
        data_load <= (stage_cntr > 0)?{dev_id,1'b1}:{dev_id,1'b0}; 
    end 
    else if(nxt_stat == PH_A0) begin
        data_load <= sub_address[15:8];    
    end
    else if(nxt_stat == PH_A1) begin
        data_load <= sub_address[7:0];     
    end
    else if((nxt_stat == PH_D0)&&(stage_cntr == 1)) begin
        data_load <= write_data[7:0];     
    end
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        eeprom_data_read <= 16'b0;        
    end 
    else if((nxt_stat == PH_D0)&&(step_cntr < 8)&&(phase_cntr == 'h6)&&posedge_div_clk) begin
        eeprom_data_read <= {eeprom_data_read[14:0],eeprom_sda};     
    end
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        eeprom_sda_o  <= 1'b1;    
    end 
    else if ((cur_stat == IDLE)||((cur_stat == PH_D0)&&(step_cntr == 8))) begin
        eeprom_sda_o  <= 1'b1;
    end
    else if(nxt_stat == cur_stat) begin
        if(posedge_div_clk&&(phase_cntr == 0)) eeprom_sda_o <= data_load[7];
    end
end
//--------------------------------------------------------
reg  eeprom_sda_t;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        eeprom_sda_t <= 1'b0;    
    end 
    else if(cur_stat == IDLE) begin 
        eeprom_sda_t <= 1'b0;    
    end 
    else if((cur_stat == PH_D0)&&(s_op == 1)) begin
        eeprom_sda_t <= (step_cntr >= 8)?1'b0:1'b1;
    end
    else if(step_cntr >= 8) begin
        eeprom_sda_t <= 1'b1;    
    end
    else begin
        eeprom_sda_t <= 1'b0;    
    end
end 
//--------------------------------------------------------
wire sclk_int = phase_cntr[3]^ phase_cntr[2];
wire sclk_gat = ((cur_stat == IDLE)||(cur_stat == START)||(cur_stat == STOP))?1'b1:1'b0;
//--------------------------------------------------------
reg eeprom_sclk_int;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        eeprom_sclk_int <= 1'b1;     
    end 
    else if(((cur_stat == START)&&(step_cntr >= 2))||
            ((cur_stat == STOP)&&(step_cntr == 0))) begin
        eeprom_sclk_int <= 1'b0;     
    end
    else begin
        eeprom_sclk_int <= sclk_gat|sclk_int;     
    end
end 
//--------------------------------------------------------
reg eeprom_sclk_o;
always @(posedge clk) eeprom_sclk_o <= eeprom_sclk_int;
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        ack_detec <= 1'b0;   
    end 
    else if((ack_detec==0)&&eeprom_sda_t&&(phase_cntr == 5))begin 
        ack_detec <= (eeprom_sda == 0)?1'b1:1'b0;  
    end 
    else if(~eeprom_sda_t)begin
        ack_detec <= 1'b0;   
    end
end 
//--------------------------------------------------------
always @(posedge clk,posedge rst) begin
    if(rst)begin 
        eeprom_trans_over <= 1'b0;
    end
    else if((cur_stat == STOP)&&(nxt_stat == IDLE))begin
        eeprom_trans_over <= 1'b1;
    end
    else begin
        eeprom_trans_over <= 1'b0;
    end
end
//--------------------------------------------------------
assign eeprom_sda   = eeprom_sda_t ? 1'hz:eeprom_sda_o;
assign eeprom_sclk  = eeprom_sclk_o;

endmodule
